Completed
Pull Request — develop (#128)
by Xaver
01:06
created

labellayer.js ➔ ... ➔ L.GridLayer.extend.setData   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
c 2
b 0
f 0
nc 2
dl 0
loc 56
rs 9.7251
nop 7

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
define(['leaflet', 'rbush', 'helper', 'moment'],
2
  function (L, rbush, helper, moment) {
3
    'use strict';
4
5
    var groupOnline;
6
    var groupOffline;
7
    var groupNew;
8
    var groupLost;
9
    var groupLines;
10
11
    var iconOnline = {
12
      color: '#1566A9',
13
      fillColor: '#1566A9',
14
      radius: 6,
15
      fillOpacity: 0.5,
16
      opacity: 0.5,
17
      weight: 2,
18
      className: 'stroke-first'
19
    };
20
    var iconOffline = {
21
      color: '#D43E2A',
22
      fillColor: '#D43E2A',
23
      radius: 3,
24
      fillOpacity: 0.5,
25
      opacity: 0.5,
26
      weight: 1,
27
      className: 'stroke-first'
28
    };
29
    var iconLost = {
30
      color: '#D43E2A',
31
      fillColor: '#D43E2A',
32
      radius: 4,
33
      fillOpacity: 0.8,
34
      opacity: 0.8,
35
      weight: 1,
36
      className: 'stroke-first'
37
    };
38
    var iconAlert = {
39
      color: '#D43E2A',
40
      fillColor: '#D43E2A',
41
      radius: 5,
42
      fillOpacity: 0.8,
43
      opacity: 0.8,
44
      weight: 2,
45
      className: 'stroke-first'
46
    };
47
    var iconNew = {
48
      color: '#1566A9',
49
      fillColor: '#93E929',
50
      radius: 6,
51
      fillOpacity: 1.0,
52
      opacity: 0.5,
53
      weight: 2
54
    };
55
56
    var labelLocations = [['left', 'middle', 0 / 8],
57
      ['center', 'top', 6 / 8],
58
      ['right', 'middle', 4 / 8],
59
      ['left', 'top', 7 / 8],
60
      ['left', 'ideographic', 1 / 8],
61
      ['right', 'top', 5 / 8],
62
      ['center', 'ideographic', 2 / 8],
63
      ['right', 'ideographic', 3 / 8]];
64
    var labelShadow;
65
    var bodyStyle = { fontFamily: 'sans-serif' };
66
    var nodeRadius = 4;
67
68
    var cFont = document.createElement('canvas').getContext('2d');
69
70
    function measureText(font, text) {
71
      cFont.font = font;
72
      return cFont.measureText(text);
73
    }
74
75
    function mapRTree(d) {
76
      return { minX: d.position.lat, minY: d.position.lng, maxX: d.position.lat, maxY: d.position.lng, label: d };
77
    }
78
79
    function prepareLabel(fillStyle, fontSize, offset, stroke) {
80
      return function (d) {
81
        var font = fontSize + 'px ' + bodyStyle.fontFamily;
82
        return {
83
          position: L.latLng(d.nodeinfo.location.latitude, d.nodeinfo.location.longitude),
84
          label: d.nodeinfo.hostname,
85
          offset: offset,
86
          fillStyle: fillStyle,
87
          height: fontSize * 1.2,
88
          font: font,
89
          stroke: stroke,
90
          width: measureText(font, d.nodeinfo.hostname).width
91
        };
92
      };
93
    }
94
95
    function calcOffset(offset, loc) {
96
      return [offset * Math.cos(loc[2] * 2 * Math.PI),
97
        -offset * Math.sin(loc[2] * 2 * Math.PI)];
98
    }
99
100
    function labelRect(p, offset, anchor, label, minZoom, maxZoom, z) {
101
      var margin = 1 + 1.41 * (1 - (z - minZoom) / (maxZoom - minZoom));
102
103
      var width = label.width * margin;
104
      var height = label.height * margin;
105
106
      var dx = {
107
        left: 0,
108
        right: -width,
109
        center: -width / 2
110
      };
111
112
      var dy = {
113
        top: 0,
114
        ideographic: -height,
115
        middle: -height / 2
116
      };
117
118
      var x = p.x + offset[0] + dx[anchor[0]];
119
      var y = p.y + offset[1] + dy[anchor[1]];
120
121
      return { minX: x, minY: y, maxX: x + width, maxY: y + height };
122
    }
123
124
    function mkMarker(dict, iconFunc, router) {
125
      return function (d) {
126
        var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
127
128
        m.resetStyle = function resetStyle() {
129
          m.setStyle(iconFunc(d));
130
        };
131
132
        m.on('click', function () {
133
          router.fullUrl({ node: d.nodeinfo.node_id });
134
        });
135
        m.bindTooltip(d.nodeinfo.hostname);
136
137
        dict[d.nodeinfo.node_id] = m;
138
139
        return m;
140
      };
141
    }
142
143
    function addLinksToMap(dict, linkScale, graph, router) {
144
      graph = graph.filter(function (d) {
145
        return 'distance' in d && !d.vpn;
146
      });
147
148
      return graph.map(function (d) {
149
        var opts = {
150
          color: linkScale(1 / d.tq),
151
          weight: 4,
152
          opacity: 0.5,
153
          dashArray: 'none'
154
        };
155
156
        var line = L.polyline(d.latlngs, opts);
157
158
        line.resetStyle = function resetStyle() {
159
          line.setStyle(opts);
160
        };
161
162
        line.bindTooltip(d.source.node.nodeinfo.hostname + ' – ' + d.target.node.nodeinfo.hostname + '<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d) + '</strong>');
163
        line.on('click', function () {
164
          router.fullUrl({ link: d.id });
165
        });
166
167
        dict[d.id] = line;
168
169
        return line;
170
      });
171
    }
172
173
    return L.GridLayer.extend({
174
      onAdd: function (map) {
175
        L.GridLayer.prototype.onAdd.call(this, map);
176
        if (this.data) {
177
          this.prepareLabels();
178
        }
179
      },
180
      setData: function (data, map, nodeDict, linkDict, linkScale, router, config) {
181
        // Check if init or data is already set
182
        if (groupLines) {
183
          groupOffline.clearLayers();
184
          groupOnline.clearLayers();
185
          groupNew.clearLayers();
186
          groupLost.clearLayers();
187
          groupLines.clearLayers();
188
        }
189
190
        var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
191
        groupLines = L.featureGroup(lines).addTo(map);
192
193
        var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
194
        var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
195
196
        var markersOnline = nodesOnline.filter(helper.hasLocation)
197
          .map(mkMarker(nodeDict, function () {
198
            return iconOnline;
199
          }, router));
200
201
        var markersOffline = nodesOffline.filter(helper.hasLocation)
202
          .map(mkMarker(nodeDict, function () {
203
            return iconOffline;
204
          }, router));
205
206
        var markersNew = data.nodes.new.filter(helper.hasLocation)
207
          .map(mkMarker(nodeDict, function () {
208
            return iconNew;
209
          }, router));
210
211
        var markersLost = data.nodes.lost.filter(helper.hasLocation)
212
          .map(mkMarker(nodeDict, function (d) {
213
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAgeAlert, 'days'))) {
214
              return iconAlert;
215
            }
216
217
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAge, 'days'))) {
218
              return iconLost;
219
            }
220
            return null;
221
          }, router));
222
223
        groupOffline = L.featureGroup(markersOffline).addTo(map);
224
        groupLost = L.featureGroup(markersLost).addTo(map);
225
        groupOnline = L.featureGroup(markersOnline).addTo(map);
226
        groupNew = L.featureGroup(markersNew).addTo(map);
227
228
        this.data = {
229
          online: nodesOnline.filter(helper.hasLocation),
230
          offline: nodesOffline.filter(helper.hasLocation),
231
          new: data.nodes.new.filter(helper.hasLocation),
232
          lost: data.nodes.lost.filter(helper.hasLocation)
233
        };
234
        this.updateLayer();
235
      },
236
      updateLayer: function () {
237
        if (this._map) {
238
          this.prepareLabels();
239
        }
240
      },
241
      prepareLabels: function () {
242
        var d = this.data;
243
244
        // label:
245
        // - position (WGS84 coords)
246
        // - offset (2D vector in pixels)
247
        // - anchor (tuple, textAlignment, textBaseline)
248
        // - minZoom (inclusive)
249
        // - label (string)
250
        // - color (string)
251
252
        var labelsOnline = d.online.map(prepareLabel(null, 11, 8, true));
253
        var labelsOffline = d.offline.map(prepareLabel('rgba(212, 62, 42, 0.9)', 9, 5, false));
254
        var labelsNew = d.new.map(prepareLabel('rgba(48, 99, 20, 0.9)', 11, 8, true));
255
        var labelsLost = d.lost.map(prepareLabel('rgba(212, 62, 42, 0.9)', 11, 8, true));
256
257
        var labels = []
258
          .concat(labelsNew)
259
          .concat(labelsLost)
260
          .concat(labelsOnline)
261
          .concat(labelsOffline);
262
263
        var minZoom = this.options.minZoom;
264
        var maxZoom = this.options.maxZoom;
265
266
        var trees = [];
267
268
        var map = this._map;
269
270
        function nodeToRect(z) {
271
          return function (n) {
272
            var p = map.project(n.position, z);
273
            return { minX: p.x - nodeRadius, minY: p.y - nodeRadius, maxX: p.x + nodeRadius, maxY: p.y + nodeRadius };
274
          };
275
        }
276
277
        for (var z = minZoom; z <= maxZoom; z++) {
278
          trees[z] = rbush(9);
279
          trees[z].load(labels.map(nodeToRect(z)));
280
        }
281
282
        labels = labels.map(function (n) {
283
          var best = labelLocations.map(function (loc) {
284
            var offset = calcOffset(n.offset, loc);
285
            var i;
286
287
            for (i = maxZoom; i >= minZoom; i--) {
288
              var p = map.project(n.position, i);
289
              var rect = labelRect(p, offset, loc, n, minZoom, maxZoom, i);
290
              var candidates = trees[i].search(rect);
291
292
              if (candidates.length > 0) {
293
                break;
294
              }
295
            }
296
297
            return { loc: loc, z: i + 1 };
298
          }).filter(function (k) {
299
            return k.z <= maxZoom;
300
          }).sort(function (a, b) {
301
            return a.z - b.z;
302
          })[0];
303
304
          if (best !== undefined) {
305
            n.offset = calcOffset(n.offset, best.loc);
306
            n.minZoom = best.z;
307
            n.anchor = best.loc;
308
309
            for (var i = maxZoom; i >= best.z; i--) {
310
              var p = map.project(n.position, i);
311
              var rect = labelRect(p, n.offset, best.loc, n, minZoom, maxZoom, i);
312
              trees[i].insert(rect);
313
            }
314
315
            return n;
316
          }
317
          return undefined;
318
        }).filter(function (n) {
319
          return n !== undefined;
320
        });
321
322
        this.margin = 16;
323
324
        if (labels.length > 0) {
325
          this.margin += labels.map(function (n) {
326
            return n.width;
327
          }).sort().reverse()[0];
328
        }
329
330
        this.labels = rbush(9);
331
        this.labels.load(labels.map(mapRTree));
332
333
        this.redraw();
334
      },
335
      createTile: function (tilePoint) {
336
        var tile = L.DomUtil.create('canvas', 'leaflet-tile');
337
338
        var tileSize = this.options.tileSize;
339
        tile.width = tileSize;
340
        tile.height = tileSize;
341
342
        if (!this.labels) {
343
          return tile;
344
        }
345
346
        var s = tilePoint.multiplyBy(tileSize);
347
        var map = this._map;
348
        bodyStyle = window.getComputedStyle(document.querySelector('body'));
349
        labelShadow = bodyStyle.backgroundColor.replace(/rgb/i, 'rgba').replace(/\)/i, ',0.7)');
350
351
        function projectNodes(d) {
352
          var p = map.project(d.label.position);
353
354
          p.x -= s.x;
355
          p.y -= s.y;
356
357
          return { p: p, label: d.label };
358
        }
359
360
        var bbox = helper.getTileBBox(s, map, tileSize, this.margin);
361
        var labels = this.labels.search(bbox).map(projectNodes);
362
        var ctx = tile.getContext('2d');
363
364
        ctx.lineWidth = 5;
365
        ctx.strokeStyle = labelShadow;
366
        ctx.miterLimit = 2;
367
368
        function drawLabel(d) {
369
          ctx.font = d.label.font;
370
          ctx.textAlign = d.label.anchor[0];
371
          ctx.textBaseline = d.label.anchor[1];
372
          ctx.fillStyle = d.label.fillStyle === null ? bodyStyle.color : d.label.fillStyle;
373
374
          if (d.label.stroke) {
375
            ctx.strokeText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
376
          }
377
378
          ctx.fillText(d.label.label, d.p.x + d.label.offset[0], d.p.y + d.label.offset[1]);
379
        }
380
381
        labels.filter(function (d) {
382
          return tilePoint.z >= d.label.minZoom;
383
        }).forEach(drawLabel);
384
385
        return tile;
386
      }
387
    });
388
  });
389